Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 37c96a5ed7f5e6e1dda990eca940f774f3846788


Parents : f7e22f2
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Signature : T66BB85Valid, signed by author
Date : 2026-07-07T02:05:10+02:00

Added half-duplex mode and call screen improvements

Changes

4 files changed, 329 insertions(+), 83 deletions(-)

M sbapp/ui/voice.py +260 -59

Diff

diff --git a/sbapp/main.py b/sbapp/main.py
index f9459288..fc3fcce1 100644
--- a/sbapp/main.py
+++ b/sbapp/main.py
@@ -1730,6 +1730,11 @@ class SidebandApp(MDApp):
self.key_ptt_down = False
self.message_ptt_up_action()
+ elif self.root.ids.screen_manager.current == "voice_screen":
+ if keycode == 44:
+ if self.key_ptt_down:
+ self.key_ptt_down = False
+ self.voice_screen.ptt_up_action()
def keydown_event(self, instance, keyboard, keycode, text, modifiers):
if self.keyboard_enabled:
@@ -1755,6 +1760,12 @@ class SidebandApp(MDApp):
c_index = keycode-29
self.conversation_index_action(c_index)
+ if self.root.ids.screen_manager.current == "voice_screen":
+ if len(modifiers) == 0 and keycode == 44:
+ if not self.key_ptt_down:
+ self.key_ptt_down = True
+ self.voice_screen.ptt_down_action()
+
if self.root.ids.screen_manager.current == "messages_screen":
if keycode == 43:
if not self.messages_view.ids.message_text.focus:

diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index d71133c9..cf18cc8b 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -2078,8 +2078,9 @@ class SidebandCore():
elif "telephone_is_ringing" in call: connection.send(self.telephone.is_ringing if self.telephone else False)
elif "telephone_caller_info" in call: connection.send(self.telephone.caller.hash if self.telephone and self.telephone.caller else None)
elif "telephone_active_profile" in call: connection.send(self.telephone.active_profile if self.telephone else None)
+ elif "telephone_active_mode" in call: connection.send(self.telephone.active_mode if self.telephone else None)
elif "telephone_set_busy" in call: connection.send(self.telephone.set_busy(call["telephone_set_busy"]) if self.telephone else False)
- elif "telephone_dial" in call: connection.send(self.telephone.dial(call["telephone_dial"], profile=call["profile"]) if self.telephone else False)
+ elif "telephone_dial" in call: connection.send(self.telephone.dial(call["telephone_dial"], profile=call["profile"], mode=call["mode"]) if self.telephone else False)
elif "telephone_hangup" in call: connection.send(self.telephone.hangup() if self.telephone else False)
elif "telephone_answer" in call: connection.send(self.telephone.answer() if self.telephone else False)
elif "telephone_set_speaker" in call: connection.send(self.telephone.set_speaker(call["telephone_set_speaker"]) if self.telephone else False)
@@ -2090,10 +2091,13 @@ class SidebandCore():
elif "telephone_get_call_log" in call: connection.send(self.telephone.get_call_log() if self.telephone else [])
elif "telephone_clear_call_log" in call: connection.send(self.telephone.clear_call_log() if self.telephone else False)
elif "telephone_switch_profile" in call: connection.send(self.telephone.switch_profile(call["telephone_switch_profile"]) if self.telephone else False)
+ elif "telephone_switch_mode" in call: connection.send(self.telephone.switch_mode(call["telephone_switch_mode"]) if self.telephone else False)
+ elif "telephone_squelch_transmit" in call: connection.send(self.telephone.squelch_transmit(call["telephone_squelch_transmit"]) if self.telephone else False)
elif "telephone_mute_mic" in call: connection.send(self.telephone.mute_mic(call["telephone_mute_mic"]) if self.telephone else False)
elif "telephone_mute_spk" in call: connection.send(self.telephone.mute_spk(call["telephone_mute_spk"]) if self.telephone else False)
elif "telephone_transmit_muted" in call: connection.send(self.telephone.transmit_muted if self.telephone else False)
elif "telephone_receive_muted" in call: connection.send(self.telephone.receive_muted if self.telephone else False)
+ elif "telephone_call_stats" in call: connection.send(self.telephone.call_stats if self.telephone else False)
else:
connection.send(None)

diff --git a/sbapp/sideband/voice.py b/sbapp/sideband/voice.py
index 786292a8..ca039472 100644
--- a/sbapp/sideband/voice.py
+++ b/sbapp/sideband/voice.py
@@ -83,6 +83,9 @@ class ReticulumTelephone():
def mute_spk(self, muted):
self.telephone.mute_receive(muted)
+ def squelch_transmit(self, squelched):
+ self.telephone.squelch_transmit(squelched)
+
def announce(self, attached_interface=None):
self.telephone.announce(attached_interface=attached_interface)
@@ -113,6 +116,24 @@ class ReticulumTelephone():
@property
def active_profile(self): return self.telephone.active_profile
+ @property
+ def active_mode(self): return self.telephone.active_mode
+
+ @property
+ def call_stats(self):
+ stats = {"time": None, "hops": None, "rxb": 0, "txb": 0}
+ if self.telephone.active_call:
+ stats["time"] = time.time()-self.telephone.active_call.established_at if self.telephone.active_call.established_at else None
+ stats["hops"] = self.telephone.active_call.expected_hops
+ stats["mode"] = self.telephone.active_call.call_mode
+ stats["profile"] = self.telephone.active_call.profile
+ stats["rxcodec"] = str(self.telephone.active_call.audio_source.codec).replace("<LXST/", "").replace(">", "") if hasattr(self.telephone.active_call, "audio_source") else "Unknown"
+ stats["txcodec"] = str(self.telephone.transmit_codec).replace("<LXST/", "").replace(">", "")
+ stats["rxb"] = self.telephone.active_call.rxbytes
+ stats["txb"] = self.telephone.active_call.txbytes
+
+ return stats
+
def start(self):
if not self.should_run:
self.should_run = True
@@ -186,7 +207,7 @@ class ReticulumTelephone():
RNS.log(f"An error occurred while updating call log: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
- def dial(self, identity_hash, profile=None):
+ def dial(self, identity_hash, profile=None, mode=None):
self.last_dialled_identity_hash = identity_hash
destination_hash = RNS.Destination.hash_from_name_and_identity("lxst.telephony", identity_hash)
if RNS.Transport.has_path(destination_hash):
@@ -194,23 +215,26 @@ class ReticulumTelephone():
cs = "" if call_hops == 1 else "s"
RNS.log(f"Connecting call over {call_hops} hop{cs}...", RNS.LOG_DEBUG)
identity = RNS.Identity.recall(destination_hash)
- self.call(identity, profile=profile)
+ self.call(identity, profile=profile, mode=mode)
else:
return "no_path"
def redial(self, args=None):
if self.last_dialled_identity_hash: self.dial(self.last_dialled_identity_hash)
- def call(self, remote_identity, profile=None):
+ def call(self, remote_identity, profile=None, mode=None):
RNS.log(f"Calling {RNS.prettyhexrep(remote_identity.hash)}...", RNS.LOG_DEBUG)
self.state = self.STATE_CONNECTING
self.caller = remote_identity
self.direction = "to"
- self.telephone.call(self.caller, profile=profile)
+ self.telephone.call(self.caller, profile=profile, mode=mode)
def switch_profile(self, profile):
self.telephone.switch_profile(profile)
+ def switch_mode(self, mode):
+ self.telephone.switch_mode(mode)
+
def ringing(self, remote_identity):
if self.hw_state == self.HW_STATE_SLEEP: self.hw_state = self.HW_STATE_IDLE
self.state = self.STATE_RINGING
@@ -296,24 +320,30 @@ class ReticulumTelephoneProxy():
def active_profile(self): return self.owner.service_rpc_request({"telephone_active_profile": True })
@property
- def transmit_muted(self):
- return self.owner.service_rpc_request({"telephone_transmit_muted": True})
+ def active_mode(self): return self.owner.service_rpc_request({"telephone_active_mode": True })
+
+ @property
+ def transmit_muted(self): return self.owner.service_rpc_request({"telephone_transmit_muted": True})
+
+ @property
+ def receive_muted(self): return self.owner.service_rpc_request({"telephone_receive_muted": True})
@property
- def receive_muted(self):
- return self.owner.service_rpc_request({"telephone_receive_muted": True})
-
- def set_busy(self, busy): return self.owner.service_rpc_request({"telephone_set_busy": busy })
- def dial(self, dial_target, profile=None): return self.owner.service_rpc_request({"telephone_dial": dial_target, "profile": profile })
- def hangup(self): return self.owner.service_rpc_request({"telephone_hangup": True })
- def answer(self): return self.owner.service_rpc_request({"telephone_answer": True })
- def set_speaker(self, speaker): return self.owner.service_rpc_request({"telephone_set_speaker": speaker })
- def set_microphone(self, microphone): return self.owner.service_rpc_request({"telephone_set_microphone": microphone })
- def set_ringer(self, ringer): return self.owner.service_rpc_request({"telephone_set_ringer": ringer })
- def set_low_latency_output(self, enabled): return self.owner.service_rpc_request({"telephone_set_low_latency_output": enabled})
- def announce(self): return self.owner.service_rpc_request({"telephone_announce": True})
- def get_call_log(self): return self.owner.service_rpc_request({"telephone_get_call_log": True})
- def clear_call_log(self): return self.owner.service_rpc_request({"telephone_clear_call_log": True})
- def switch_profile(self, profile): return self.owner.service_rpc_request({"telephone_switch_profile": profile})
- def mute_mic(self, muted): return self.owner.service_rpc_request({"telephone_mute_mic": muted})
- def mute_spk(self, muted): return self.owner.service_rpc_request({"telephone_mute_spk": muted})
\ No newline at end of file
+ def call_stats(self): return self.owner.service_rpc_request({"telephone_call_stats": True})
+
+ def set_busy(self, busy): return self.owner.service_rpc_request({"telephone_set_busy": busy })
+ def dial(self, dial_target, profile=None, mode=None): return self.owner.service_rpc_request({"telephone_dial": dial_target, "profile": profile, "mode": mode })
+ def hangup(self): return self.owner.service_rpc_request({"telephone_hangup": True })
+ def answer(self): return self.owner.service_rpc_request({"telephone_answer": True })
+ def set_speaker(self, speaker): return self.owner.service_rpc_request({"telephone_set_speaker": speaker })
+ def set_microphone(self, microphone): return self.owner.service_rpc_request({"telephone_set_microphone": microphone })
+ def set_ringer(self, ringer): return self.owner.service_rpc_request({"telephone_set_ringer": ringer })
+ def set_low_latency_output(self, enabled): return self.owner.service_rpc_request({"telephone_set_low_latency_output": enabled})
+ def announce(self): return self.owner.service_rpc_request({"telephone_announce": True})
+ def get_call_log(self): return self.owner.service_rpc_request({"telephone_get_call_log": True})
+ def clear_call_log(self): return self.owner.service_rpc_request({"telephone_clear_call_log": True})
+ def switch_profile(self, profile): return self.owner.service_rpc_request({"telephone_switch_profile": profile})
+ def switch_mode(self, mode): return self.owner.service_rpc_request({"telephone_switch_mode": mode})
+ def squelch_transmit(self, squelched): return self.owner.service_rpc_request({"telephone_squelch_transmit": squelched})
+ def mute_mic(self, muted): return self.owner.service_rpc_request({"telephone_mute_mic": muted})
+ def mute_spk(self, muted): return self.owner.service_rpc_request({"telephone_mute_spk": muted})
\ No newline at end of file

diff --git a/sbapp/ui/voice.py b/sbapp/ui/voice.py
index 2963c4f7..eb1a0cbd 100644
--- a/sbapp/ui/voice.py
+++ b/sbapp/ui/voice.py
@@ -31,29 +31,30 @@ else:
from kivy.utils import escape_markup
if RNS.vendor.platformutils.get_platform() == "android":
- from ui.helpers import multilingual_markup
+ from ui.helpers import multilingual_markup, mdc
else:
- from .helpers import multilingual_markup
+ from .helpers import multilingual_markup, mdc
from LXST.Primitives.Telephony import Telephone, Profiles
class Voice():
def __init__(self, app):
- self.app = app
- self.screen = None
- self.settings_screen = None
- self.dial_target = None
- self.ui_updater = None
- self.path_requesting = None
- self.output_devices = []
- self.input_devices = []
- self.log_list = None
- self.last_log_update = 0
- self.log_name_cache = {}
+ self.app = app
+ self.screen = None
+ self.settings_screen = None
+ self.dial_target = None
+ self.ui_updater = None
+ self.path_requesting = None
+ self.output_devices = []
+ self.input_devices = []
+ self.log_list = None
+ self.last_log_update = 0
+ self.log_name_cache = {}
self.listed_output_devices = []
- self.listed_input_devices = []
+ self.listed_input_devices = []
self.listed_ringer_devices = []
- self.call_profile = Profiles.DEFAULT_PROFILE
+ self.call_profile = Profiles.DEFAULT_PROFILE
+ self.call_mode = Profiles.DEFAULT_MODE
if not self.app.root.ids.screen_manager.has_screen("voice_screen"):
self.screen = Builder.load_string(layout_voice_screen)
@@ -61,6 +62,24 @@ class Voice():
self.screen.delegate = self
self.app.root.ids.screen_manager.add_widget(self.screen)
self.update_call_log()
+
+ self.screen.ids.log_controls._target_meta = "log_controls"
+ self.screen.ids.call_controls._target_meta = "audio_controls"
+ for w in self.screen.ids.meta_controls.children:
+ if hasattr(w, "_target_meta"):
+ if w._target_meta == "log_controls": self._log_controls = w
+ elif w._target_meta == "audio_controls": self._audio_controls = w
+
+ self.screen.ids.meta_controls.remove_widget(self._audio_controls)
+
+ def widget_hide(self, w, hide=True):
+ if hasattr(w, "saved_attrs"):
+ if not hide:
+ w.height, w.size_hint_y, w.opacity, w.disabled = w.saved_attrs
+ del w.saved_attrs
+ elif hide:
+ w.saved_attrs = w.height, w.size_hint_y, w.opacity, w.disabled
+ w.height, w.size_hint_y, w.opacity, w.disabled = 0, None, 0, True
def update_call_status(self, dt=None):
if self.app.root.ids.screen_manager.current == "voice_screen":
@@ -74,8 +93,16 @@ class Voice():
rb = self.screen.ids.reject_button
ih = self.screen.ids.identity_hash
pb = self.screen.ids.call_profile_button
+ mb = self.screen.ids.call_mode_button
+ tb = self.screen.ids.call_ptt_button
+ tc = self.screen.ids.call_ptt_container
rm = self.screen.ids.spk_mute_button
tm = self.screen.ids.mic_mute_button
+ ll = self.screen.ids.log_list_container
+ lc = self.screen.ids.log_controls
+ cc = self.screen.ids.call_controls
+ cs = self.screen.ids.call_stats_container
+ sl = self.screen.ids.call_stats_label
if self.app.sideband.voice_running:
telephone = self.app.sideband.telephone
if self.path_requesting:
@@ -88,11 +115,13 @@ class Voice():
ih.disabled = False
rb.disabled = True
pb.disabled = False
+ mb.disabled = False
self.target_input_action(ih)
else:
ih.disabled = True
rb.disabled = True
pb.disabled = True
+ mb.disabled = True
if telephone.is_in_call or telephone.call_is_connecting:
ih.disabled = True
@@ -100,26 +129,51 @@ class Voice():
db.disabled = False
if telephone.call_is_connecting: pb.disabled = True
if telephone.is_in_call: pb.disabled = False
+ if telephone.is_in_call: mb.disabled = False
db.text = "Hang up"
db.icon = "phone-hangup"
if telephone.active_profile: self.call_profile = telephone.active_profile
+ if telephone.active_mode: self.call_mode = telephone.active_mode
elif telephone.is_ringing:
ih.disabled = True
rb.disabled = False
db.disabled = False
pb.disabled = True
+ mb.disabled = True
db.text = "Answer"
db.icon = "phone-ring"
if telephone.caller: ih.text = RNS.hexrep(telephone.caller.hash, delimit=False)
if telephone.active_profile: self.call_profile = telephone.active_profile
+ if telephone.active_mode: self.call_mode = telephone.active_mode
if telephone.is_in_call:
rm.disabled = False
tm.disabled = False
+ tb.disabled = False
else:
rm.disabled = True
tm.disabled = True
+ tb.disabled = True
+
+ if telephone.is_in_call or telephone.call_is_connecting or telephone.is_ringing:
+ def cb(dt=None):
+ self.widget_hide(ll, True); self.widget_hide(cs, False)
+ if self.call_mode == Profiles.MODE_HALF_DUPLEX: self.widget_hide(tc, False)
+ else: self.widget_hide(tc, True)
+ if self._log_controls in self.screen.ids.meta_controls.children:
+ self.screen.ids.meta_controls.remove_widget(self._log_controls)
+ if not self._audio_controls in self.screen.ids.meta_controls.children:
+ self.screen.ids.meta_controls.add_widget(self._audio_controls)
+ Clock.schedule_once(cb, 0.1)
+ else:
+ def cb(dt=None):
+ self.widget_hide(ll, False); self.widget_hide(cs, True)
+ if self._audio_controls in self.screen.ids.meta_controls.children:
+ self.screen.ids.meta_controls.remove_widget(self._audio_controls)
+ if not self._log_controls in self.screen.ids.meta_controls.children:
+ self.screen.ids.meta_controls.add_widget(self._log_controls)
+ Clock.schedule_once(cb, 0.1)
if telephone.receive_muted:
rm.text = "Output Muted"
@@ -135,6 +189,35 @@ class Voice():
tm.text = "Mic Active"
tm.icon = "microphone"
+ if telephone.is_in_call or telephone.call_is_connecting or telephone.is_ringing:
+ stats = telephone.call_stats
+ t_str = RNS.prettytime(int(stats["time"])) if stats["time"] else None
+ rxb = stats["rxb"]
+ txb = stats["txb"]
+
+
+ if telephone.is_ringing:
+ disp_name = peer_disp_name = multilingual_markup(escape_markup(str(self.app.sideband.voice_display_name(telephone.caller.hash))).encode("utf-8")).decode("utf-8")
+ r_str = f"Call from {disp_name}"
+ else:
+ disp_name = peer_disp_name = multilingual_markup(escape_markup(str(self.app.sideband.voice_display_name(self.dial_target))).encode("utf-8")).decode("utf-8")
+ r_str = f"Call with {disp_name}"
+
+ s_str = f""
+ if t_str: s_str += f"\nConnected for {t_str}\n"
+ else: s_str += f"\nConnecting call...\n" if not telephone.is_ringing else ""
+
+ h_str = f"{stats['hops']} hop{'' if stats['hops'] == 1 else 's'}" if stats["hops"] else "" if stats["hops"] else "Unknown"
+ s_str += f"\nDistance : {h_str}"
+ s_str += f"\nMode : {Profiles.mode_name(stats['mode']) if stats['mode'] else 'Unknown'}"
+ s_str += f"\nProfile : {Profiles.profile_name(stats['profile']) if stats['profile'] else 'Unknown'}"
+ # s_str += f"\nRX Codec : {stats['rxcodec']}"
+ s_str += f"\nCodec : {stats['txcodec']}"
+ s_str += f"\nTraffic : TX {RNS.prettysize(txb)}"
+ s_str += f"\n RX {RNS.prettysize(rxb)}"
+
+ sl.text = f"[b]{r_str}[/b]\n[font=RobotoMono-Regular][size={int(dp(14))}]{s_str}[/size][/font]"
+
if self.app.sideband.getstate("voice.connection_failure"):
self.app.sideband.setstate("voice.connection_failure", False)
toast("Could not connect call", duration=5)
@@ -144,11 +227,39 @@ class Voice():
ih.disabled = True
rb.disabled = True
pb.disabled = True
+ mb.disabled = True
pb.text = Profiles.profile_abbrevation(self.call_profile)
+ mb.text = Profiles.mode_abbrevation(self.call_mode)
+ if self.call_mode == Profiles.MODE_FULL_DUPLEX: mb.icon = "phone-incoming-outgoing"
+ else: mb.icon = "radio-handheld"
if time.time() > self.last_log_update+3: self.update_call_log()
+ def ptt_down_action(self, sender=None):
+ ptt_button = self.screen.ids.call_ptt_button
+ ptt_icon = self.screen.ids.call_ptt_button.children[0].children[1]
+ ptt_button.theme_text_color="Custom"
+ ptt_button.text_color=mdc("Orange","400")
+ ptt_button.line_color=mdc("Orange","400")
+ ptt_icon.theme_text_color="Custom"
+ ptt_icon.text_color=mdc("Orange","400")
+
+ def job(): self.app.sideband.telephone.squelch_transmit(False)
+ threading.Thread(target=job, daemon=True).start()
+
+ def ptt_up_action(self, sender=None):
+ ptt_button = self.screen.ids.call_ptt_button
+ ptt_icon = self.screen.ids.call_ptt_button.children[0].children[1]
+ ptt_button.theme_text_color="Custom"
+ ptt_button.text_color=mdc("BlueGray","500")
+ ptt_button.line_color=mdc("BlueGray","500")
+ ptt_icon.theme_text_color="Custom"
+ ptt_icon.text_color=mdc("BlueGray","500")
+
+ def job(): self.app.sideband.telephone.squelch_transmit(True)
+ threading.Thread(target=job, daemon=True).start()
+
def target_valid(self):
if self.app.sideband.voice_running:
db = self.screen.ids.dial_button
@@ -193,7 +304,7 @@ class Voice():
self.app.sideband.telephone.set_busy(False)
if RNS.Transport.has_path(self.path_requesting):
RNS.log(f"Calling {RNS.prettyhexrep(self.dial_target)}...", RNS.LOG_DEBUG)
- self.app.sideband.telephone.dial(self.dial_target, profile=self.call_profile)
+ self.app.sideband.telephone.dial(self.dial_target, profile=self.call_profile, mode=self.call_mode)
Clock.schedule_once(self.update_call_status, 0.1)
else:
@@ -214,6 +325,17 @@ class Voice():
pb.text = Profiles.profile_abbrevation(self.call_profile)
toast(f"Call Profile: {Profiles.profile_name(self.call_profile)}")
+ def call_mode_action(self, sender=None):
+ if self.app.sideband.telephone.is_in_call: self.switch_mode_action()
+ else:
+ mb = self.screen.ids.call_mode_button
+ if self.call_mode == Profiles.MODE_FULL_DUPLEX: self.call_mode = Profiles.MODE_HALF_DUPLEX
+ else: self.call_mode = Profiles.MODE_FULL_DUPLEX
+ mb.text = Profiles.mode_abbrevation(self.call_mode)
+ if self.call_mode == Profiles.MODE_FULL_DUPLEX: mb.icon = "phone-incoming-outgoing"
+ else: mb.icon = "radio-handheld"
+ toast(f"Call Mode: {Profiles.mode_name(self.call_mode)}")
+
def switch_profile_action(self, sender=None):
if self.initial_call_profile == None: self.initial_call_profile = self.call_profile
if self.initial_call_profile < Profiles.QUALITY_MEDIUM: alt_profile = Profiles.QUALITY_MEDIUM
@@ -226,6 +348,12 @@ class Voice():
RNS.log(f"Switching to {Profiles.profile_name(switch_profiles[0])}", RNS.LOG_DEBUG)
self.app.sideband.telephone.switch_profile(switch_profiles[0])
+ def switch_mode_action(self, sender=None):
+ if self.call_mode == Profiles.MODE_FULL_DUPLEX: mode = Profiles.MODE_HALF_DUPLEX
+ else: mode = Profiles.MODE_FULL_DUPLEX
+ RNS.log(f"Switching to {Profiles.mode_name(mode)}", RNS.LOG_DEBUG)
+ self.app.sideband.telephone.switch_mode(mode)
+
def clear_log_action(self, sender=None):
self.app.sideband.telephone.clear_call_log()
self.update_call_log()
@@ -258,7 +386,7 @@ class Voice():
if not RNS.Transport.has_path(destination_hash): self.request_path(destination_hash)
else:
RNS.log(f"Calling {RNS.prettyhexrep(self.dial_target)}...", RNS.LOG_DEBUG)
- if self.app.sideband.telephone.dial(self.dial_target, profile=self.call_profile) == "no_path":
+ if self.app.sideband.telephone.dial(self.dial_target, profile=self.call_profile, mode=self.call_mode) == "no_path":
self.request_path(destination_hash)
self.update_call_status()
@@ -581,6 +709,17 @@ MDScreen:
height: self.minimum_height
padding: [dp(0), dp(0), dp(0), dp(0)]
+ MDRectangleFlatIconButton:
+ id: call_mode_button
+ icon: "phone-incoming-outgoing"
+ text: "FDX"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.call_mode_action(self)
+ disabled: False
+
MDRectangleFlatIconButton:
id: call_profile_button
icon: "account-voice"
@@ -592,6 +731,24 @@ MDScreen:
on_release: root.delegate.call_profile_action(self)
disabled: False
+ MDBoxLayout:
+ orientation: "horizontal"
+ spacing: "24dp"
+ size_hint_y: None
+ height: self.minimum_height
+ padding: [dp(0), dp(0), dp(0), dp(0)]
+
+ MDRectangleFlatIconButton:
+ id: reject_button
+ icon: "phone-cancel"
+ text: "Reject"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.reject_action(self)
+ disabled: True
+
MDRectangleFlatIconButton:
id: dial_button
icon: "phone-outgoing"
@@ -599,21 +756,10 @@ MDScreen:
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
- size_hint: [2.0, None]
+ size_hint: [1.0, None]
on_release: root.delegate.dial_action(self)
disabled: True
- MDRectangleFlatIconButton:
- id: reject_button
- icon: "phone-cancel"
- text: "Reject"
- padding: [dp(0), dp(14), dp(0), dp(14)]
- icon_size: dp(24)
- font_size: dp(16)
- size_hint: [1.0, None]
- on_release: root.delegate.reject_action(self)
- disabled: True
-
MDSeparator:
orientation: "horizontal"
height: dp(1)
@@ -622,55 +768,110 @@ MDScreen:
orientation: "vertical"
id: log_list_container
+ MDBoxLayout:
+ orientation: "vertical"
+ id: call_stats_container
+ spacing: "24dp"
+ padding: [dp(28), dp(24), dp(28), dp(24)]
+
+ MDAnchorLayout:
+ anchor_y: 'top'
+
+ MDLabel:
+ id: call_stats_label
+ markup: True
+ text: "Call stats"
+ size_hint_y: None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ padding: [dp(0), dp(0), dp(0), dp(0)]
+
MDSeparator:
orientation: "horizontal"
height: dp(1)
MDBoxLayout:
+ id: meta_controls
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
- spacing: "24dp"
- padding: [dp(28), dp(24), dp(28), dp(24)]
-
- MDRectangleFlatIconButton:
- id: clear_log_button
- icon: "playlist-remove"
- text: "Clear Log"
- padding: [dp(0), dp(14), dp(0), dp(14)]
- icon_size: dp(24)
- font_size: dp(16)
- size_hint: [1.0, None]
- on_release: root.delegate.clear_log_action(self)
+ spacing: "0dp"
+ padding: [dp(0), dp(0), dp(0), dp(0)]
MDBoxLayout:
- orientation: "horizontal"
- spacing: "24dp"
+ id: log_controls
+ orientation: "vertical"
size_hint_y: None
height: self.minimum_height
- padding: [dp(0), dp(0), dp(0), dp(0)]
+ spacing: "24dp"
+ padding: [dp(28), dp(24), dp(28), dp(24)]
MDRectangleFlatIconButton:
- id: mic_mute_button
- icon: "microphone"
- text: "Mic Active"
+ id: clear_log_button
+ icon: "playlist-remove"
+ text: "Clear Log"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
- on_release: root.delegate.mic_mute_action(self)
- disabled: False
+ on_release: root.delegate.clear_log_action(self)
- MDRectangleFlatIconButton:
- id: spk_mute_button
- icon: "volume-high"
- text: "Output Active"
- padding: [dp(0), dp(14), dp(0), dp(14)]
- icon_size: dp(24)
- font_size: dp(16)
- size_hint: [1.0, None]
- on_release: root.delegate.spk_mute_action(self)
- disabled: False
+ MDBoxLayout:
+ id: call_controls
+ orientation: "vertical"
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: "0dp"
+ padding: [dp(28), dp(24), dp(28), dp(24)]
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ spacing: "24dp"
+ size_hint_y: None
+ height: self.minimum_height
+ padding: [dp(0), dp(0), dp(0), dp(0)]
+
+ MDRectangleFlatIconButton:
+ id: mic_mute_button
+ icon: "microphone"
+ text: "Mic Active"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.mic_mute_action(self)
+ disabled: False
+
+ MDRectangleFlatIconButton:
+ id: spk_mute_button
+ icon: "volume-high"
+ text: "Output Active"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.spk_mute_action(self)
+ disabled: False
+
+ MDBoxLayout:
+ id: call_ptt_container
+ orientation: "horizontal"
+ spacing: "0dp"
+ size_hint_y: None
+ height: self.minimum_height
+ padding: [dp(0), dp(24), dp(0), dp(0)]
+
+ MDRectangleFlatIconButton:
+ id: call_ptt_button
+ icon: "microphone"
+ text: "PTT"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_press: root.delegate.ptt_down_action(self)
+ on_release: root.delegate.ptt_up_action(self)
+ _no_ripple_effect: True
"""
layout_voice_settings_screen = """


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────